interface TypingConfig {
    minTime: number;
    maxTime: number;
    callback?: () => void;
    targetString: string;
    cursorString: string;
    cursorInterval: number;
    render?: (outString: string) => void;
}

type TypingOptions = Partial<TypingConfig>;

function rand(min: number, max: number) {
    return min + Math.round(Math.random() * (max - min));
}

/**
 * 打字机特效
 */
export default class Typing {
    public config: TypingConfig = {
        minTime: 100,
        maxTime: 300,
        callback: undefined,
        targetString: "",
        cursorString: "|",
        cursorInterval: 500,
        render: (outString) => {
            console.clear();
            console.log(outString);
        },
    };

    public currentString: string = "";
    public currentIndex: number = 0;
    public hasCursor: boolean = true;
    public cursorTimer: number = 0;

    constructor(options?: TypingOptions) {
        if (options) this.updateConfig(options);
    }

    public run(options?: string | TypingOptions) {
        if (typeof options === "string") {
            this.config.targetString = options;
        }
        if (typeof options === "object") {
            this.updateConfig(options);
        }

        this.currentIndex = 0;
        this.currentString = "";

        return new Promise((resolve) => {
            const next = async () => {
                this.config.targetString += "";

                this.stopCursor(); // 停止光标
                this.runCursor(); // 开启光标（竞争）

                if (this.currentIndex < this.config.targetString.length) {
                    this.currentString += this.config.targetString[this.currentIndex];
                    this.currentIndex++;
                } else {
                    this.currentString = this.config.targetString;
                }

                await this.render();

                if (this.currentIndex < this.config.targetString.length) {
                    setTimeout(
                        next.bind(this), // 递归自身，并传参
                        rand(this.config.minTime, this.config.maxTime)
                    );
                } else {
                    if (resolve) resolve(true); // 所有字符自增完成
                    if (this.config.callback) this.config.callback();
                }

            }
            next();
        });
    }

    public updateConfig(options?: TypingOptions) {
        if (options) this.config = { ...this.config, ...options };
    }

    public render(): Promise<any> | void {
        return this.config.render?.(this.outString);
    }

    public get outString() {
        return (
            this.currentString + (this.hasCursor ? this.config.cursorString : "")
        );
    }

    public runCursor() {
        this.cursorTimer = setInterval(
            this.toggleCursor.bind(this),
            this.config.cursorInterval
        );
    }

    public stopCursor() {
        clearInterval(this.cursorTimer);
        this.hasCursor = true;
    }

    public toggleCursor() {
        this.hasCursor = !this.hasCursor;
        this.render();
    }
}
